/**
 * @file filter
 * @author liaoxuezhi@baidu.com
 */
/* eslint fecs-indent: [0, "space", 2, 2] */

import * as React from 'react';
import {
  PureComponent,
  PropTypes
} from 'react';
import * as cx from 'classnames';
import {
  FormControl,
  InputGroup,
  Button
} from 'react-bootstrap';
import {fetch,filter,setVariable} from '../../renderer';
import TextareaAutosize from 'react-textarea-autosize';
import * as assign from 'object-assign';
import validationErrors from '../validation-messages';
import {Enhance as FormItem} from '../form-item';
import {findDOMNode} from 'react-dom';
import {evalExpression} from '../../util';
import * as Autosuggest from 'react-autosuggest';
import Html from '../../../widgets/html';
import * as debounce from 'lodash/debounce';
import './text.scss';
import * as merge from 'lodash/merge';
import * as cloneDeep from 'lodash/cloneDeep';
import {filterOptions} from '../util';

const theme = {
  container:                'react-autosuggest-container',
  containerOpen:            'react-autosuggest-container-open',
  input:                    'react-autosuggest-input form-control',
  inputOpen:                'react-autosuggest-input-open',
  inputFocused:             'react-autosuggest-input-focused',
  suggestionsContainer:     'react-autosuggest-suggestions-container',
  suggestionsContainerOpen: 'react-autosuggest-suggestions-container-open',
  suggestionsList:          'react-autosuggest-suggestions-list',
  suggestion:               'react-autosuggest-suggestion',
  suggestionFirst:          'react-autosuggest-suggestion-first',
  suggestionHighlighted:    'react-autosuggest-suggestion-highlighted',
  sectionContainer:         'react-autosuggest-section-container',
  sectionContainerFirst:    'react-autosuggest-section-container-first',
  sectionTitle:             'react-autosuggest-section-title'
};

const defaultAutoComplete = {
  api: '',
  matchHighlight: true,
  lazySeconds: 500,
  maxCount: 100,
  matchReg: '/${value}/gi',
  theme: theme,
  reqOn: 'true',
  tpl: '',
  reqField: 'value',
  multiple: false,
  delimiter: ';',
  disableAutoFill: false
};

@FormItem({
  type: 'text',
  test: (path, config) => /(?:^|\/)form-item$/.test(path) && ~['text', 'textarea', 'password', 'email', 'number', 'hidden', 'url'].indexOf(config.type),
  filter: config => {
    let validations = config.validations || (config.type === 'email' ? 'isEmail' : config.type === 'url' ? 'isUrl': '');

    if (config.maxLength) {
      validations += (validations ? ',' : '') + `maxLength:${config.maxLength}`
    }

    if (config.minLength) {
      validations += (validations ? ',' : '') + `minLength:${config.minLength}`
    }

    return assign({
      validations: validations,
      validationErrors
    }, config);
  },
  validateSchema: 'http://amis.baidu.com/schemas/form.json#/definitions/formItem'
})
export default class FormText extends PureComponent {
  static propTypes = {
    className: PropTypes.string,
    formMode: PropTypes.string,
    formHorizontal: PropTypes.object,
    inputClassName: PropTypes.string,
    onRequestAction: PropTypes.func,
    addOn: PropTypes.shape({
      actionType: PropTypes.string
    }),
    inline: PropTypes.bool
  };

  static defaultProps = {
    value: '',
    minRows: 3,
    maxRows: 20,
    inlineLabelClassName: '',
    autoComplete: {}
  };

  suggestions = [];

  constructor(props) {
    super(props);
    this.suggestions = props.sourceLocalData ? filterOptions(props.sourceLocalData, props.data) : (filterOptions(props.options, props.data) || []);
    this.state = {
      suggestions: this.suggestions,
      autoComplete: this.generateAutoComplete(props)
    };

    this.loadRemote = this.loadRemote.bind(this);
    this.clearSuggestions = this.clearSuggestions.bind(this);
    this.getSuggestionValue = this.getSuggestionValue.bind(this);
    this.renderSuggestion = this.renderSuggestion.bind(this);
    this.onSuggestionSelected = this.onSuggestionSelected.bind(this);
    this.changeValue = this.changeValue.bind(this);
    this.handleAddOnClick = this.handleAddOnClick.bind(this);
    this.lazyLoadRemote = debounce(this.loadRemote, props.lazySeconds, {
      leading: false,
      trailing: true
    });
    this.sugFilterLocal = this.sugFilterLocal.bind(this);
    this.onSuggestionsFetchRequested = this.onSuggestionsFetchRequested.bind(this);
    this.generateReg = this.generateReg.bind(this);
    this.renderSuggestionsContainer = this.renderSuggestionsContainer.bind(this);
    this.sugMultipleLocalFilter = this.sugMultipleLocalFilter.bind(this);
    this.getValue = this.getValue.bind(this);
    this.setValue = this.setValue.bind(this);
    this.getUniqueSuggestions = this.getUniqueSuggestions.bind(this);
    this.renderInputComponent = this.renderInputComponent.bind(this);
    this.handleBlur = this.handleBlur.bind(this);
  }


  componentWillReceiveProps(nextProps) {

    const maxCount = this.state.autoComplete.maxCount;
    const {sourceData} = nextProps;

    if (sourceData !== this.props.sourceData) {
      this.suggestions = filterOptions(sourceData && sourceData.hasOwnProperty('options') ? sourceData.options : sourceData);
      this.setState({
        suggestions: this.suggestions.slice(0, maxCount),
      });
    }

    if (nextProps.autoComplete !== this.props.autoComplete) {
      this.setState({
        autoComplete: this.generateAutoComplete(nextProps)
      });
    }

    if (this.props.sourceLocalData !== nextProps.sourceLocalData) {
      this.suggestions = filterOptions(nextProps.sourceLocalData, nextProps.data);
      this.setState({
        suggestions: this.suggestions.slice(0, maxCount),
      });
    }
  }

  generateAutoComplete(props) {
    let autoComplete = props.autoComplete;
    if (!autoComplete) {
      return '';
    }
    if(typeof autoComplete == 'string') {
      autoComplete = {
        api: autoComplete
      };
    }
    return merge(cloneDeep(defaultAutoComplete), autoComplete);
  }



  changeValue(event) {
    this.props.setValue(event.currentTarget.value);
  }

  handleAddOnClick() {
    const {
      onRequestAction,
      addOn
    } = this.props;

    onRequestAction && onRequestAction(addOn);
  }

  clearSuggestions() {

  }

  onSuggestionsFetchRequested({value}) {
    const {
      source,
      name,
      data,
      options,
      sourceLocalData
    } = this.props;

    const {
      autoComplete: {
        reqOn,
        multiple
      }
    } = this.state;

    if (source || options || sourceLocalData) {
      multiple ? this.sugMultipleLocalFilter(value) : this.sugFilterLocal(value);
    } else if(evalExpression(reqOn, merge(data, setVariable(name, value)))) {
      value ? this.lazyLoadRemote(value) : this.loadRemote('');
    } else {
      this.setState({
        suggestions: []
      });
    }
  }

  generateReg(regStr, needGroup, value) {
    const reg = /\/(.*)\/([igm]*)/;
    value = value == undefined ? this.props.getValue() : value;

    if (!regStr || !/\/.*\/[igm]*/.test(regStr)) {
      return null;
    }

    let [_, regRes, optRes] = reg.exec(regStr);
    regRes = regRes.replace('${value}', value);

    if (!~regRes.indexOf('(') && needGroup) {
      regRes = `(${regRes})`;
    }

    return new RegExp(regRes, optRes);
  }

  sugFilterLocal(value) {
    const {
      suggestions
    } = this;

    const {
      autoComplete: {
        maxCount,
        matchReg
      }
    } = this.state;

    const newSuggestions = suggestions.filter((item) => {
      return this.generateReg(matchReg, false, value).test(String(item.value))
    });

    this.setState({
      suggestions: newSuggestions.slice(0, maxCount)
    });
  };

  sugMultipleLocalFilter(value = '') {

    let {
      suggestions
    } = this;

    const {
      autoComplete: {
        maxCount,
        matchReg,
        delimiter
      }
    } = this.state;

    const valueArr = value.split(delimiter);

    const lastValue = valueArr.pop();
    suggestions = this.getUniqueSuggestions(suggestions, value);

    const newSuggestions = suggestions.filter((item) => {
      return this.generateReg(matchReg, false, lastValue).test(String(item.value))
    });

    this.setState({
      suggestions: newSuggestions.slice(0, maxCount)
    });
  }

  getUniqueSuggestions(suggestions, value) {
    const {
      autoComplete: {
        delimiter,
        multiple
      }
    } = this.state;
    value = (value === void 0 ? this.props.getValue() : value) ||  '';

    if(!multiple) {
      return suggestions;
    }

    const valueArr = value.split(delimiter);

    valueArr.pop();
    const valueMap = {};

    valueArr.forEach(item => valueMap[item] = true);

    return suggestions.filter((item) => {
      return !valueMap[String(item.value)]
    });
  }

  loadRemote(input) {
    const {
      data,
      name
    } = this.props;

    const {
      autoComplete: {
        api,
        reqField
      }
    } = this.state;

    if (!api) {
      return;
    }
    input = this.getValue(input);

    this.setState({
      loading: true
    }, () => {
      fetch(api, {[reqField]: input}, 'get', merge(data, setVariable(name, input)))
      .then(ret => {
        this.setState({
          suggestions: this.getUniqueSuggestions(filterOptions(ret.data.options || ret.data, data)),
          loading: false
        });
      });
    });
  }

  getSuggestionValue(item) {
      return item['value'];
  }

  handleBlur() {
    const {
      submitOnBlur,
      submitForm,
      isPristine
    } = this.props;

    if (submitOnBlur && !isPristine()) {
      setTimeout(submitForm, 500);
    }
  }

  renderSuggestion(item) {
    const {
      getValue,
      data
    } = this.props;

    const {
      autoComplete: {
        matchHighlight,
        matchReg,
        tpl
      }
    } = this.state;

    const value = this.getValue();

    if (tpl) {
      item =  assign({}, data, item);
      return <Html html={filter(tpl, item)} />
    } else {
      item = item['value'];
      return matchHighlight ? (<Html html={item.replace(this.generateReg(matchReg, true, value), '<span class="react-autosuggest-suggestions-matched">$1</span>')} />) : (<Html html={item} />);
    }
  }

  getValue(value) {
    const {
      autoComplete: {
        multiple,
        delimiter
      }
    } = this.state;

    value = (value === void 0 ? this.props.getValue() : value) ||  '';
    if (this.props.getValue() === 0 || this.props.getValue() === '0') {
        value = this.props.getValue();
    }
    if (multiple) {
      let index = value.lastIndexOf(delimiter) + 1;

      return value.substring(index);
    }
    return value;
  }

  setValue(value) {
    const {
      autoComplete: {
        multiple,
        delimiter
      }
    } = this.state;
    let prevValue = this.props.getValue() || '';
    if (this.props.getValue() === 0 || this.props.getValue() === '0') {
        prevValue = this.props.getValue();
    }
    if(multiple) {
      let index = prevValue.lastIndexOf(delimiter) + 1;

      prevValue = prevValue.substring(0, index);
    }
    multiple ? this.props.setValue(prevValue + value + delimiter) : this.props.setValue(value);
  }

  onSuggestionSelected(event, value) {
    event.preventDefault();
    this.setValue(value.suggestionValue);
  }

  shouldRenderSuggestions() {
    return true;
  }

  renderSuggestionsContainer(props) {
    const loading = this.state.loading;
    const suggestions = this.state.suggestions;
    return loading ? (<div {...props.containerProps}>
        <i key="loading" className="fa fa-spinner fa-spin fa-2x fa-fw" /></div>
      ) : (
        <div {...props.containerProps}>{props.children}</div>
      );
  }

  renderInputComponent(inputProps) {
    const {
      loading
    } = this.state;

    return loading ? (<div className="react-autosuggest-input-zone-loading"><input {...inputProps} /> <span className="Select-loading-zone"><span className="Select-loading"></span></span></div>) : (<div><input {...inputProps} /></div>)
  }

  focus() {
    let el = findDOMNode(this.input);
    el && el.focus();
  }

  render() {
    let {
      disabled,
      inputClassName,
      placeholder,
      required,
      readOnly,
      type,
      addOn,
      data,
      minRows,
      maxRows,
      inline,
      min,
      max,
      step,
      theme,
      maxLength,
      source,
      options,
      style,
      width,
      height,
      disableAutoFill
    } = this.props;

    const loading = this.state.loading;
    const autoComplete = this.state.autoComplete;
    let value = this.props.getValue() || '';
    if (this.props.getValue() === 0 || this.props.getValue() === '0') {
        value = this.props.getValue();
    }
    if (type === 'hidden') {
      return null;
    }

    if (width || height) {
      style = assign({}, style, {
        width,
        height
      });
    }

    const commonProps = {
      className: cx('form-control',inputClassName || inline && 'w-auto'),
      disabled,
      type,
      key:"input",
      placeholder,
      onChange: this.changeValue,
      onBlur: this.handleBlur,
      value,
      required,
      readOnly,
      min,
      max,
      step,
      style,
      autoComplete: type === 'password' && disableAutoFill ? 'new-password' : undefined
    };

    let input;
    if (type === 'textarea') {
      input = (
        <FormControl
          {...commonProps}
          componentClass={TextareaAutosize}
          minRows={minRows}
          maxRows={maxRows}
          ref={input => this.input = input}
        />
      );
    } else if(autoComplete && autoComplete.api || source || options){
      input = (
        <Autosuggest
          suggestions={this.state.suggestions}
          onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
          onSuggestionsClearRequested={this.clearSuggestions}
          getSuggestionValue={this.getSuggestionValue}
          renderSuggestion={this.renderSuggestion}
          onSuggestionSelected = {this.onSuggestionSelected}
          shouldRenderSuggestions = {this.shouldRenderSuggestions}
          inputProps={commonProps}
          renderInputComponent = {this.renderInputComponent}
          theme = {autoComplete.theme}
        />
      );
    } else {
      input = (
        <FormControl
          {...commonProps}
          ref={input => this.input = input}
        />
      );
    }

    if (addOn) {
      input = (
        <InputGroup key="input-group" className={inputClassName}>
          {input}
          {addOn.actionType || addOn.type === 'button' || addOn.type === 'submit' ? (
            <InputGroup.Button>
              <Button
                type={addOn.type === 'submit' ? "submit" : "button"}
                disabled={disabled || addOn.disabledOn && evalExpression(addOn.disabledOn, data)}
                className={cx(addOn.className, {
                  'btn-primary': addOn.primary
                })}
                onClick={this.handleAddOnClick}
              >
                {addOn.label}
                {addOn.icon ? (<i className={addOn.icon} />) : null}
              </Button>
            </InputGroup.Button>
          ) : (
            <InputGroup.Addon
              className={cx(addOn.className, {
                'btn-primary': addOn.primary
              })}
            >
              {addOn.label}
              {addOn.icon && (<i className={addOn.icon} />)}
            </InputGroup.Addon>
          )}
        </InputGroup>
      );
    }

    if (maxLength) {
      input = (
        <div className="with-statistics">
          {input}
          <span className="text-count">
            {`还可以输入${Math.max(maxLength - (value && value.length || 0), 0)}个字`}
          </span>
        </div>
      );
    }

    return (
      input
    );
  }
}
